/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2007 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Web;
using System.Threading;
using System.Collections.Generic;

using Gtk;
using Glade;
using GLib;
using Pango;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;
using Galaxium.Gui;
using Galaxium.Gui.GtkGui;

namespace Galaxium.Protocol.Msn.GtkGui
{
	public class MsnChatWidget : BasicChatWidget
	{
		// Actual GUI widgets that will be used within a MSN specific chat window.
		protected P2PViewWebcam _webcam = null;
		protected BasicContactTreeView _contactTree = null;
		
		// Common properties that we can include.
		public override bool ShowActionToolbar
		{
			get { return (Conversation.PrimaryContact as MsnContact).ShowActionToolbar; }
			set { (Conversation.PrimaryContact as MsnContact).ShowActionToolbar = value; if (value) _window.Toolbar.ShowAll(); else _window.Toolbar.Visible = false; }
		}
		
		public override bool ShowInputToolbar
		{
			get { return (Conversation.PrimaryContact as MsnContact).ShowInputToolbar; }
			set { (Conversation.PrimaryContact as MsnContact).ShowInputToolbar = value; if (value) _entryToolbar.ShowAll(); else _entryToolbar.Visible = false; }
		}
		
		public override bool ShowAccountImage
		{
			get { return (Conversation.PrimaryContact as MsnContact).ShowAccountImage; }
			set { (Conversation.PrimaryContact as MsnContact).ShowAccountImage = value; if (value) _leftInputBox.ShowAll(); else _leftInputBox.Visible = false; }
		}
		
		public override bool ShowContactImage
		{
			get { return (Conversation.PrimaryContact as MsnContact).ShowContactImage; }
			set { (Conversation.PrimaryContact as MsnContact).ShowContactImage = value; if (value) _rightInputBox.ShowAll(); else _rightInputBox.Visible = false; }
		}
		
		public override bool ShowPersonalMessage
		{
			get { return (Conversation.PrimaryContact as MsnContact).ShowPersonalMessage; }
			set { (Conversation.PrimaryContact as MsnContact).ShowPersonalMessage = value; if (value) _identificationSpacer.ShowAll(); else _identificationSpacer.Visible = false; }
		}
		
		public override bool ShowTimestamps
		{
			get { return (Conversation.PrimaryContact as MsnContact).ShowTimestamps; }
			set { (Conversation.PrimaryContact as MsnContact).ShowTimestamps = value; }
		}
		
		public override bool UseDefaultView
		{
			get { return (Conversation.PrimaryContact as MsnContact).UseDefaultView; }
			set { (Conversation.PrimaryContact as MsnContact).UseDefaultView = value; }
		}
		
		public override bool EnableLogging
		{
			get { return Conversation.PrimaryContact.Logging; }
			set
			{
				if (_messageDisplay != null)
				{
					if (value)
						_messageDisplay.AddSystemMessage ("You are now logging this conversation.");
					else
						_messageDisplay.AddSystemMessage ("You are no longer logging this conversation.");
				}
				
				Conversation.PrimaryContact.Logging = value;
			}
		}
		
		public override bool EnableSounds
		{
			get { return (Conversation.PrimaryContact as MsnContact).EnableSounds; }
			set { (Conversation.PrimaryContact as MsnContact).EnableSounds = value; }
		}
		
		public override bool UseDefaultSettings
		{
			get { return (Conversation.PrimaryContact as MsnContact).UseDefaultSettings; }
			set { (Conversation.PrimaryContact as MsnContact).UseDefaultSettings = value; }
		}
		
		internal MsnChatWidget (IContainerWindow<Widget> window, IConversation conversation) : base (window, conversation)
		{
			// Here is where we can connect all the events that need to be mapped for the chat window to work.
			_conversation.Established += ConversationEstablishedEvent;
			_conversation.Closed += ConversationClosedEvent;
			_conversation.ContactJoined += ConversationContactJoinedEvent;
			_conversation.ContactLeft += ConversationContactLeftEvent;
			_conversation.AllContactsLeft += ConversationAllContactsLeftEvent;
			_conversation.MessageReceived += ConversationMessageReceivedEvent;
			(_conversation as MsnConversation).InkReceived += ConversationInkReceivedEvent;
			(_conversation as MsnConversation).NudgeReceived += ConversationNudgeReceivedEvent;
			(_conversation as MsnConversation).TypingReceived += ConversationContactTypingEvent;
			(_conversation as MsnConversation).WinkReceived += ConversationWinkReceived;
			(_conversation as MsnConversation).VoiceClipReceived += ConversationVoiceClipReceived;
			(_conversation as MsnConversation).MessageSendFailed += ConversationMessageSendFailed;
			(_conversation as MsnConversation).WebcamInviteReceived += ConversationWebcamInviteReceived;
			(_conversation as MsnConversation).CapabilitiesChanged += ConversationCapabilitiesChanged;
			(_conversation as MsnConversation).ActionMessageReceived += ConversationActionMessageReceived;
			
			_conversation.Session.Account.DisplayImageChange += AccountDisplayImageChange;
		}
		
		public override void Initialize ()
		{ 
			// You must call this before anything else.
			base.Initialize ();
			
			_contactTree = new BasicContactTreeView ();
			
			// Add any extra contacts into the list.
			foreach (MsnContact contact in Conversation.ContactCollection)
			{
				_contactTree.AddNode (contact);
				AddEventHandlers (contact);
			}
			
			SetChatWidget (ChatWidgetPositions.RightDisplayPane, _contactTree);
			
			AdjustForContacts ();
			
			// Load up the conversation history logs.
			
			IConversationLog log = Conversation.ConversationLog;
			int n = _logging_config.GetInt("HistorySize", 5);
			bool enableLogging = UseDefaultSettings ? _config.GetBool (Configuration.Conversation.EnableLogging.Name, Configuration.Conversation.EnableLogging.Default) : EnableLogging;
			
			if (enableLogging && n > 0)
			{
				List<ConversationLogEntry> entries = new List<ConversationLogEntry> ();
				entries.AddRange (log.GetNLastEntries (n));
				
				if (entries.Count > 0)
				{
					try
					{
						foreach (ConversationLogEntry entry in entries)
						{
							if (entry.IsEvent)
							{
								_messageDisplay.AddSystemMessage (entry.Message);
								continue;
							}
							
							MsnTextMessage message = new MsnTextMessage ((_conversation.Session as MsnSession).FindEntity (entry.UniqueIdentifier),
							                                             entry.TimeStamp, new MsnTextStyle (), entry.Message);
							
							_messageDisplay.AddHistoryMessage (message);
						}
					}
					catch (Exception ex)
					{
						Anculus.Core.Log.Error (ex, "Error displaying history");
					}
				}
			}
			
			_historyComplete = true;
			
			Update();
			
			_nativeWidget.Show ();
		}
		
		protected override void BecomeReady (object sender, EventArgs args)
		{
			base.BecomeReady (sender, args);
			//Anculus.Core.Log.Debug ("Ready: {0}", _nativeWidget.GdkWindow);
			(_conversation as MsnConversation).ListenerReady = true;
		}
		
		public override void SwitchTo ()
		{
			MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/Toolbar", new DefaultExtensionContext (this), _window.Toolbar as Toolbar);
			MenuUtility.FillMenuBar ("/Galaxium/Gui/MSN/ContainerWindow/Menu", new DefaultExtensionContext (this), _window.Menubar as MenuBar);
			MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
		}
		
		public override void Destroy ()
		{
			(_conversation as MsnConversation).ListenerReady = false;
			
			_conversation.Established -= ConversationEstablishedEvent;
			_conversation.Closed -= ConversationClosedEvent;
			_conversation.ContactJoined -= ConversationContactJoinedEvent;
			_conversation.ContactLeft -= ConversationContactLeftEvent;
			_conversation.AllContactsLeft -= ConversationAllContactsLeftEvent;
			_conversation.MessageReceived -= ConversationMessageReceivedEvent;
			(_conversation as MsnConversation).InkReceived -= ConversationInkReceivedEvent;
			(_conversation as MsnConversation).NudgeReceived -= ConversationNudgeReceivedEvent;
			(_conversation as MsnConversation).TypingReceived -= ConversationContactTypingEvent;
			(_conversation as MsnConversation).WinkReceived -= ConversationWinkReceived;
			(_conversation as MsnConversation).VoiceClipReceived -= ConversationVoiceClipReceived;
			(_conversation as MsnConversation).MessageSendFailed -= ConversationMessageSendFailed;
			(_conversation as MsnConversation).WebcamInviteReceived -= ConversationWebcamInviteReceived;
			(_conversation as MsnConversation).CapabilitiesChanged -= ConversationCapabilitiesChanged;
			(_conversation as MsnConversation).ActionMessageReceived -= ConversationActionMessageReceived;
		}
		
		/*private void OwnerTimerElapsed(object o, EventArgs oArgs)
		{
			 (delegate
			{
				int length = _messageEntry.Buffer.Text.Length;
				
				if (length < 1)
				{
					lock (_ownerTimer)
						_ownerTimer.Enabled = false;
				}
				else
					(_conversation as MsnConversation).SendTyping ();
			});
		}*/
		
		[GLib.ConnectBefore	()]
		protected override void KeyReleaseEvent (object sender, KeyReleaseEventArgs args)
		{
			(_conversation as MsnConversation).Typing = _messageEntry.Text.Length > 0;
		}
		
		[GLib.ConnectBefore ()]
		protected override void KeyPressEvent (object sender, KeyPressEventArgs args)
		{
			Gdk.Key key = args.Event.Key;
			uint state = ((uint)args.Event.State);
			state &= 1101u;
			const uint Normal = 0, Shift = 1, Control = 4;//, ShiftControl = 5, Alt = 8;
			
			MsnContact contact = Conversation.PrimaryContact as MsnContact;
			
			if (contact == null)
			{
				Anculus.Core.Log.Error ("There is no primary contact to send messages to in this conversation!");
				return;
			}
			
			switch (state)
			{
				case Normal:
					if (key == Gdk.Key.Return || key == Gdk.Key.KP_Enter)
					{
						if (_messageEntry.Text.Trim().Length < 1)
						{
							args.RetVal = true;
							return;
						}
					
						if (!(_conversation as MsnConversation).CanSendMessage)
						{
							_messageDisplay.AddSystemMessage ("Galaxium is unable to send a message to this contact");
						
							args.RetVal = true;
							return;
						}
						
						MsnTextStyle style = GetStyle () as MsnTextStyle;
						
						MsnTextMessage tmessage = new MsnTextMessage(_conversation.Session.Account, DateTime.Now, style, _messageEntry.Text);

						ProcessOutgoingMessage (tmessage);
					
						_messageEntry.Clear();
						
						_messageDisplay.AddMessage(tmessage);
						
						args.RetVal = true;
						
						return;
					}
					break;
				case Shift:
					break;
				case Control:
					break;
				default:
					break;
			}
		}
		
		private void ConversationEstablishedEvent (object sender, ConversationEventArgs args)
		{
			
		}
		
		private void ConversationClosedEvent (object sender, ConversationEventArgs args)
		{
			
		}
		
		void ConversationContactJoinedEvent (object sender, ContactActionEventArgs args)
		{
			_contactTree.AddNode (args.Contact);
			
			AdjustForContacts ();
			
			if (Conversation.ContactCollection.Count > 1)
				_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (args.Contact.DisplayIdentifier, args.Contact.Session.Account.Protocol) +
				                                  " has joined the conversation");
			
			if (args.Contact != Conversation.PrimaryContact)
				AddEventHandlers (args.Contact as MsnContact);
		}
		
		void ConversationContactLeftEvent (object sender, ContactActionEventArgs args)
		{
			foreach (uint node in _contactTree.GetNodes (args.Contact))
				_contactTree.RemoveNode (node);
			
			AdjustForContacts ();
			
			if (Conversation.ContactCollection.Count > 1)
				_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (args.Contact.DisplayIdentifier, args.Contact.Session.Account.Protocol) + " has left the conversation");
			
			if (args.Contact != Conversation.PrimaryContact)
				RemoveEventHandlers (args.Contact as MsnContact);
		}
		
		void AddEventHandlers (MsnContact contact)
		{
			contact.DisplayImageChange += ContactDisplayImageChanged;
			contact.DisplayMessageChange += ContactDisplayMessageChanged;
			contact.DisplayNameChange += ContactDisplayNameChanged;
			contact.PresenceChange += ContactPresenceChanged;
			contact.SupressChange += ContactSupressChanged;
		}
		
		void RemoveEventHandlers (MsnContact contact)
		{
			contact.DisplayImageChange -= ContactDisplayImageChanged;
			contact.DisplayMessageChange -= ContactDisplayMessageChanged;
			contact.DisplayNameChange -= ContactDisplayNameChanged;
			contact.PresenceChange -= ContactPresenceChanged;
			contact.SupressChange -= ContactSupressChanged;
		}
		
		void ConversationAllContactsLeftEvent (object sender, ConversationEventArgs args)
		{
			
		}
		
		void ConversationActionMessageReceived (object sender, ActionMessageEventArgs args)
		{
			_messageDisplay.AddSystemMessage (args.Message);
			
			EmitBecomeActive ();
		}
		
		private void ConversationMessageReceivedEvent (object sender, TextMessageEventArgs args)
		{
			//Log.Info ("MessageReceived {0}", args.Message.GetText ());
			ProcessIncomingMessage (args.Message);
		}
		
		private void ConversationInkReceivedEvent (object sender, InkEventArgs args)
		{
			_messageDisplay.AddImageMessage (args.Source, args.InkData);
			
			EmitBecomeActive ();
		}
		
		private void ConversationNudgeReceivedEvent (object sender, ContactEventArgs args)
		{
		
			_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (args.Contact.DisplayIdentifier, args.Contact.Session.Account.Protocol) +
			                                  " is nudging you to get your attention!");
			
			EmitBecomeActive ();
			
			if (_config.GetBool("WindowShake", true))
				_window.Shake();
			
			SoundSetUtility.Play(Sound.Nudge);
		}
		
		private void ConversationContactTypingEvent (object sender, ContactEventArgs args)
		{
			_activityImage.FromAnimation = IconUtility.GetAnimation("galaxium-typing_anim");
			
			_status_label.Text = MessageUtility.StripMarkup (args.Contact.DisplayIdentifier, args.Contact.Session.Account.Protocol) +" is now typing a message...";
			EmitBecomingActive ();
		}
		
		private void ContactSupressChanged (object sender, EntityChangeEventArgs<bool> args)
		{
			if ((Conversation.PrimaryContact as MsnContact).SupressImage)
				_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (IconUtility.GetIcon ("galaxium-displayimage", IconSizes.Huge), 96, 96));
			else
			{
				try
				{
					_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (new Gdk.Pixbuf(Conversation.PrimaryContact.DisplayImage.ImageBuffer), 96, 96));
				}
				catch
				{
					_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (IconUtility.GetIcon ("galaxium-displayimage", IconSizes.Huge), 96, 96));
				}
			}
		}
		
		private void ConversationWinkReceived (object sender, WinkEventArgs args)
		{
			_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (args.Wink.Creator.DisplayIdentifier, args.Wink.Creator.Session.Account.Protocol) +
			                                  " has sent you the wink '" + args.Wink.Friendly +"'");
			
			EmitBecomeActive ();
			
			DisplayWink (args.Wink);
		}
		
		void ConversationVoiceClipReceived (object sender, VoiceClipEventArgs args)
		{
			_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (args.Clip.Creator.DisplayIdentifier, args.Clip.Creator.Session.Account.Protocol) +
			                                  " has sent you a voice clip");
			EmitBecomeActive ();
			
			args.Clip.Request (delegate
			{
				SoundUtility.Play (args.Clip.CacheFilename);
			});
		}
		
		void ConversationWebcamInviteReceived (object sender, InviteReceivedEventArgs args)
		{
			_webcam = args.Application as P2PViewWebcam;
			_webcam.FrameReceived += WebcamFrameReceived;
			_webcam.P2PSession.Closed += WebcamClosed;
			_webcam.P2PSession.Activated += WebcamActivated;
			
			_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (args.Application.Remote.DisplayIdentifier, args.Application.Remote.Session.Account.Protocol) +
			                                  " has invited you to view their webcam.");
			EmitBecomeActive ();
			
			//TODO: we need a gui to display/accept/decline invitations
			/*_webcam.P2PSession.Waiting += delegate
			{
				_webcam.Accept ();
			};*/
		}
		
		void WebcamFrameReceived (object sender, WebcamFrameReceivedEventArgs args)
		{
			try
			{
				_contact_image.SwitchTo (new Gdk.Pixbuf (args.Data, false, 8, args.Width, args.Height, args.Width * 3, null));
			}
			catch (Exception)
			{
				Anculus.Core.Log.Warn ("Unable to display frame");
			}
		}
		
		void WebcamActivated (object sender, EventArgs args)
		{
			_contact_image.ShowLoading ();
			_contact_image.SetSizeRequest (320, 240);
		}
		
		void WebcamClosed (object sender, EventArgs args)
		{
			MsnContact contact = _webcam.Remote;
			_webcam = null;
			
			_contact_image.SetSizeRequest (96, 96);
			
			if (_webcam == null)
				SwitchContactImage (contact);
		}
		
		void ContactDisplayNameChanged (object sender, EntityChangeEventArgs<string> args)
		{
			_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (args.Old, args.Entity.Session.Account.Protocol) +
			                                  " has changed their name to: " +
			                                  MessageUtility.StripMarkup (args.New, args.Entity.Session.Account.Protocol));
			
			foreach (uint node in _contactTree.GetNodes (args.Entity))
				_contactTree.UpdateNode (node);
		}
		
		void ContactDisplayMessageChanged (object sender, EntityChangeEventArgs<string> args)
		{
			//MsnContact contact = args.Entity as MsnContact;
			
			GeneratePersonalLabel ();
		}

		void GeneratePersonalLabel ()
		{
			if (_conversation.PrimaryContact.DisplayMessage != null && _conversation.PrimaryContact.DisplayMessage != String.Empty)
				_personalLabel.Markup = "Message: "+XmlUtility.GetEncodedString (_conversation.PrimaryContact.DisplayMessage);
			else
				_personalLabel.Markup = "Message: No personal message.";
		}
		
		void ContactDisplayImageChanged (object sender, EntityChangeEventArgs<IDisplayImage> args)
		{
			MsnContact contact = args.Entity as MsnContact;
			
			if (contact == _conversation.PrimaryContact)
			{
				// If the contact has an image, but we've not yet got the data
				// don't switch the image, this will be called again once the data
				// is retrieved
				if ((contact.DisplayImage != null) && (contact.DisplayImage.ImageBuffer.Length == 0))
					return;
				
					if (_webcam == null)
						SwitchContactImage (contact);
			}
		}
		
		void ContactPresenceChanged (object sender, EntityChangeEventArgs<IPresence> args)
		{
			MsnContact contact = args.Entity as MsnContact;
			
			_messageDisplay.AddSystemMessage (MessageUtility.StripMarkup (contact.DisplayIdentifier, MsnProtocol.Instance) +
			                                  " is now " + args.New.State);
			
			foreach (uint node in _contactTree.GetNodes (args.Entity))
				_contactTree.UpdateNode (node);
		}
		
		/*private void ContactChangedEvent (object sender, ContactEventArgs args)
		{
			MsnContact contact = args.Contact as MsnContact;
			
		}*/
		
		private void AccountDisplayImageChange (object sender, EventArgs args)
		{
			_own_image.FadeTo (GenerateOwnImage ());
		}
		
		private void ProcessIncomingMessage (ITextMessage message)
		{
			if (message is MsnOfflineTextMessage)
				_messageDisplay.AddOfflineMessage(message);
			else
				_messageDisplay.AddMessage (message);
			
			foreach (MsnEmoticon emot in (message as MsnTextMessage).CustomEmoticons)
			emot.DataChanged += delegate { _messageDisplay.UpdateEmoticon (emot); };
			
			if (message is MsnOfflineTextMessage)
				_status_label.Text = "Last message received at: "+(message as MsnOfflineTextMessage).TimeStamp.ToShortTimeString()+" - "+(message as MsnOfflineTextMessage).TimeStamp.ToShortDateString();
			else
				_status_label.Text = "Last message received at: "+DateTime.Now.ToShortTimeString()+" - "+DateTime.Now.ToShortDateString();
			
			_activityImage.FromPixbuf = IconUtility.GetIcon("galaxium-typing_stopped");
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
			
			SoundSetUtility.Play(Sound.MessageReceived);
		}
		
		private void ProcessOutgoingMessage (MsnTextMessage message)
		{
			// This is where we spawn to send the message in question.
			(_conversation as MsnConversation).SendMessage(message);
			
			SoundSetUtility.Play(Sound.MessageSent);
		}
		
		public void SendNudge ()
		{
			(_conversation as MsnConversation).SendNudge();
			
				if (Conversation.ContactCollection.Count == 1)
					_messageDisplay.AddSystemMessage ("You sent " +
					                                  MessageUtility.StripMarkup (Conversation.PrimaryContact.DisplayIdentifier, MsnProtocol.Instance) +
					                                  " a nudge to get their attention!");
				else
					_messageDisplay.AddSystemMessage ("You sent a nudge!");
				
				if (_config.GetBool("WindowShake", true))
					_window.Shake ();
			
			SoundSetUtility.Play(Sound.Nudge);
		}
		
		public void SendFile ()
		{
			FileChooserDialog dialog = new FileChooserDialog("Select File", null, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept);
			dialog.SetCurrentFolder(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
			
			if (dialog.Run () == (int)ResponseType.Accept)
			{
				SendFile(dialog.Filename);
			}
			
			dialog.Destroy();
		}
		
		public override void SendFile (string filename)
		{
			if (Conversation.PrimaryContact == null)
			{
				Anculus.Core.Log.Warn ("There is no primary contact in this conversation to send files to!");
				return;
			}
			
			// Check to make sure that the file exists.
			if (File.Exists (filename))
			{
				MsnFileTransfer transfer = new MsnFileTransfer (Conversation.Session, Conversation.PrimaryContact, filename);
				
				MsnP2PUtility.Add (transfer.P2PTransfer);
				(Conversation.Session as MsnSession).EmitTransferInvitationSent (new FileTransferEventArgs (transfer));
			}
			else
			{
				string error = "The URI of the file you dropped is invalid!\n"+filename;
				MessageDialog errordialog = new MessageDialog (null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, error);
				errordialog.Run ();
				errordialog.Destroy ();
			}
		}
		
		internal void ShowActivityPopup (Gtk.Widget position)
		{
			HTMLPopup popup = new HTMLPopup (position, (_conversation as MsnConversation).ActivityPageUrl);
			popup.ShowAll ();
		}
		
		void AdjustForContacts ()
		{
			if (_window.CurrentWidget == this)
				_window.GenerateTitle ();
			
			if (_conversation.ContactCollection.Count > 1)
			{
				ShowBox (ChatWidgetPositions.RightDisplayPane);
				HideBox (ChatWidgetPositions.RightInput);
				
				_window.Toolbar.ShowAll();
				_entryToolbar.ShowAll();
				
				ShowBox (ChatWidgetPositions.LeftInput);
				HideBox (ChatWidgetPositions.Identification);
			}
			else
			{
				HideBox (ChatWidgetPositions.RightDisplayPane);
				ShowBox (ChatWidgetPositions.RightInput);
				
				if (Conversation.PrimaryContact != null)
				{
					MsnContact contact = Conversation.PrimaryContact as MsnContact;
					
					if (contact.ShowActionToolbar)
						_window.Toolbar.ShowAll();
					else
						_window.Toolbar.Visible = false;
					
					if (contact.ShowInputToolbar)
						_entryToolbar.ShowAll();
					else
						_entryToolbar.Visible = false;
					
					if (contact.ShowAccountImage)
						ShowBox (ChatWidgetPositions.LeftInput);
					else
						HideBox (ChatWidgetPositions.LeftInput);
					
					if (contact.ShowContactImage)
						ShowBox (ChatWidgetPositions.RightInput);
					else
						HideBox (ChatWidgetPositions.RightInput);
					
					if (contact.ShowPersonalMessage)
						ShowBox (ChatWidgetPositions.Identification);
					else
						HideBox (ChatWidgetPositions.Identification);
					
					GeneratePersonalLabel ();
					
					if (_webcam == null)
						SwitchContactImage (contact);
				}
				else
				{
					Anculus.Core.Log.Error ("There is no primary contact for the conversation this chat widget represents!");
				}
			}
		}
		
		void ConversationMessageSendFailed (object sender, TextMessageEventArgs args)
		{
			_messageDisplay.AddSystemMessage (string.Format ("The message \"{0}\" could not be sent within a reasonable amount of time!", args.Message.GetText ()));
			EmitBecomeActive ();
		}
		
		void ConversationCapabilitiesChanged (object sender, EventArgs args)
		{
			if (_window.CurrentWidget == this)
			{
				MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/Toolbar", new DefaultExtensionContext (this), _window.Toolbar as Toolbar);
				MenuUtility.FillMenuBar ("/Galaxium/Gui/MSN/ContainerWindow/Menu", new DefaultExtensionContext (this), _window.Menubar as MenuBar);
				MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
			}
		}
		
#region Font Handling
		public override void LoadFont ()
		{
			IConfigurationSection config = Configuration.MessageDisplay.Section[_conversation.Session.Account.UniqueIdentifier]["Font"];
			
			_messageEntry.Family = config.GetString (Configuration.MessageDisplay.Family.Name, Configuration.MessageDisplay.Family.Default);
			_messageEntry.Size = config.GetDouble (Configuration.MessageDisplay.Size.Name, Configuration.MessageDisplay.Size.Default);
			_messageEntry.ColorInt = config.GetInt (Configuration.MessageDisplay.Color.Name, Configuration.MessageDisplay.Color.Default);
			_messageEntry.Bold = config.GetBool (Configuration.MessageDisplay.Bold.Name, Configuration.MessageDisplay.Bold.Default);
			_messageEntry.Italic = config.GetBool (Configuration.MessageDisplay.Italic.Name, Configuration.MessageDisplay.Italic.Default);
			_messageEntry.Underline = config.GetBool (Configuration.MessageDisplay.Underline.Name, Configuration.MessageDisplay.Underline.Default);
			_messageEntry.Strikethrough = config.GetBool (Configuration.MessageDisplay.Strikethrough.Name, Configuration.MessageDisplay.Strikethrough.Default);
			
			MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
		}
		
		public override void SaveFont ()
		{
			IConfigurationSection config = Configuration.MessageDisplay.Section[_conversation.Session.Account.UniqueIdentifier]["Font"];

			config.SetString (Configuration.MessageDisplay.Family.Name, _messageEntry.Family);
			config.SetDouble (Configuration.MessageDisplay.Size.Name, _messageEntry.Size);
			config.SetInt (Configuration.MessageDisplay.Color.Name, _messageEntry.ColorInt);
			config.SetBool (Configuration.MessageDisplay.Bold.Name, _messageEntry.Bold);
			config.SetBool (Configuration.MessageDisplay.Italic.Name, _messageEntry.Italic);
			config.SetBool (Configuration.MessageDisplay.Underline.Name, _messageEntry.Underline);
			config.SetBool (Configuration.MessageDisplay.Strikethrough.Name, _messageEntry.Strikethrough);
			
			if (_fontChangedHandlers[_conversation.Session] != null)
				_fontChangedHandlers[_conversation.Session] (this, EventArgs.Empty);
		}
		
		private TextStyle GetStyle ()
		{
			MsnTextStyle style = new MsnTextStyle (_messageEntry.Family, (int)_messageEntry.Size, false, MsnCharacterSet.Default, MsnPitchFamily.Default);

			style.Bold = _messageEntry.Bold;
			style.Italic = _messageEntry.Italic;
			style.Underline = _messageEntry.Underline;
			style.Strikethrough = _messageEntry.Strikethrough;
			style.ForeColor = _messageEntry.ColorInt;
			
			return style;
		}
#endregion
		
		protected override void OwnImageButtonPressEvent (object sender, ButtonPressEventArgs args)
		{
			if (args.Event.Type == Gdk.EventType.TwoButtonPress)
				new SetDisplayImageCommand().Run ();
		}
		
		internal void DisplayWink (MsnWink wink)
		{
			if (!GtkUtility.EnableSwf)
			{
				_messageDisplay.AddSystemMessage ("Install swfdec 0.6 to enable wink playback");
				return;
			}
			
			wink.Request (delegate
			{
				// We now have the wink data
				
				//Log.Debug ("Ready to play wink {0}", args.Wink.Animation);
				
				//TODO: Queue display until window becomes active
				try
				{
					new WinkDisplay (wink, GetBox (ChatWidgetPositions.LeftDisplayPane)).Show ();
				}
				catch (Exception ex)
				{
					Anculus.Core.Log.Error (ex, "Error displaying wink");
				}
			});
		}
	}
}
